package com.icesoft.base.manager.config.interceptor;

import com.icesoft.base.manager.config.log.BindRequestLogInfo;
import com.icesoft.base.manager.entity.manager.RequestLog;
import com.icesoft.base.manager.entity.manager.RequestLogConfig;
import com.icesoft.base.manager.service.manager.RequestLogConfigService;
import com.icesoft.base.manager.service.manager.RequestLogService;
import com.icesoft.core.Const;
import com.icesoft.core.common.helper.Resp;
import com.icesoft.core.common.util.JsonUtil;
import com.icesoft.core.web.base.BaseHandlerInterceptor;
import com.icesoft.core.web.helper.RequestHold;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;

@Component
@Slf4j
public class RequestLogInterceptor extends BaseHandlerInterceptor {

    private RequestLogConfigService requestMappingLogInfoService;
    private RequestLogService requestLogService;
    private BindRequestLogInfo bindRequestLogInfo;

    public RequestLogInterceptor(RequestLogConfigService requestMappingLogInfoService, RequestLogService requestLogService, ObjectProvider<BindRequestLogInfo> bindRequestLogInfo) {
        this.requestMappingLogInfoService = requestMappingLogInfoService;
        this.requestLogService = requestLogService;
        this.bindRequestLogInfo = bindRequestLogInfo.getIfAvailable(() -> new BindRequestLogInfo());
    }

    /**
     * 进入controller层之前拦截请求
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        if (log.isTraceEnabled()) {
            Enumeration<String> headers = request.getHeaderNames();
            StringBuilder sb = new StringBuilder("remote ip:" + RequestHold.getRemoteIP(request)).append("\n");
            sb.append("请求 url: " + request.getRequestURI()).append("\n");
            sb.append("----------request headers---------- \n");
            while (headers.hasMoreElements()) {
                String name = headers.nextElement();
                String val = request.getHeader(name);
                sb.append(name).append(" = ").append(val).append("\n");
            }
            sb.append("----------request params---------- \n");
            RequestHold.getRequestParamMap(request).forEach((key, val) -> {
                sb.append(key).append(" = ").append(val).append("\n");
            });

            log.trace(sb.toString());
        }
        long startTime = System.currentTimeMillis();
        request.setAttribute("requestStartTime", startTime);

        if ("GET".equals(request.getMethod())) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Class<?> returnType = handlerMethod.getReturnType().getMethod().getReturnType();
        if (returnType.isAssignableFrom(ModelAndView.class)) {
            return true;
        }
        boolean isAjax = RequestHold.isAjax(request);
        boolean isResp = returnType.isAssignableFrom(Resp.class);
        if (isAjax && !isResp) {
            log.debug("{}请求返回值不规范，推荐使用Resp做为响应值", request.getRequestURI());
        } else if (isResp && !isAjax) {
            log.debug("请求url:{}的请求头x-requested-with错误：{}，请设置为：XMLHttpRequest", request.getRequestURI(),
                    request.getHeader("x-requested-with"));
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) {
        if (!(handler instanceof HandlerMethod) || modelAndView != null) {
            return;
        }
        String url = RequestHold.getRequestUrl(request);
        long startTime = (Long) request.getAttribute("requestStartTime");
        RequestLogConfig requestLogConfig = requestMappingLogInfoService.findByUrl(url);
        RequestLogConfig.LogLevel logLevel = requestLogConfig == null ? RequestLogConfig.LogLevel.debug : requestLogConfig.getLogLevel();
        if (logLevel == RequestLogConfig.LogLevel.off) {
            log.trace("请求:{}，关闭记录", url);
            return;
        }

        long executeTime = System.currentTimeMillis() - startTime;
        if (executeTime > 1000 && logLevel == RequestLogConfig.LogLevel.debug) {// 超过3s的请求记警告日志
            logLevel = RequestLogConfig.LogLevel.warn;
            log.warn("请求:{}，执行时间过长：{}ms", url, executeTime);
        }

        if (logLevel != RequestLogConfig.LogLevel.db) {
            if ("GET".equals(request.getMethod())) {
                logLevel = RequestLogConfig.LogLevel.trace;
            }
        }
        String contextType = request.getContentType();
        String param = RequestHold.getRequestParamString(request);
        log(logLevel, "请求url:{}，method:{}，contentType：{}， 执行耗时 : {}ms, 参数:{}", url, request.getMethod(), contextType,
                executeTime, param);
        Object resp = request.getAttribute(Const.RESPONSE_ATTRIBUTE_RESP);
        if (resp == null) {
            resp = "响应值不是resp，无法记录";
        }
        log(logLevel, "响应结果：{}", resp);

        if (logLevel == RequestLogConfig.LogLevel.db) {
            log.debug("请求:{}，保存记录到数据库中", url);
            RequestLog requestLog = new RequestLog();
            requestLog.setResult(JsonUtil.toJson(resp));
            requestLog.setExecuteTimeMillis(executeTime);
            requestLog.setRequestLog(RequestHold.getRemoteIP(request), url, param, request.getMethod());
            if (bindRequestLogInfo != null) {
                bindRequestLogInfo.editRequestLog(request, requestLog);
            }
            requestLogService.save(requestLog);
        }

    }

    private void log(RequestLogConfig.LogLevel level, String format, Object... arguments) {
        switch (level) {
            case trace:
                if (log.isTraceEnabled()) {
                    log.trace(format, arguments);
                }
                break;
            case debug:
                if (log.isDebugEnabled()) {
                    log.debug(format, arguments);
                }
                break;
            case info:
                if (log.isInfoEnabled()) {
                    log.info(format, arguments);
                }
                break;
            case warn:
                if (log.isWarnEnabled()) {
                    log.warn(format, arguments);
                }
                break;
            case error:
                if (log.isErrorEnabled()) {
                    log.error(format, arguments);
                }
                break;
            default:
                log.trace(format, arguments);
                break;
        }
    }

    @Override
    public String pathPatten() {
        return "/**";
    }

}
